package edu.gatech.cc.hwa.server;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import edu.gatech.cc.hwa.server.features.IpChecker;


public class Profile {
	
	public static final int MAX_HISTORY_ENTRIES = 10;
	private static final int MIN_NEEDED_TO_MATCH = 7;
	
	private Integer broswerVendor;
	private Integer dayOfWeek;
	private Collection<HistoryEntry> entries = new LinkedList<HistoryEntry>();
	private String ipAddress;
	private Integer operatingSystem;
	private final Integer profileId;
	private Integer timeOfDay;
	private Integer timeToTypePassword;
	private final Integer userId;
	
	public Profile(Integer userId, Integer profileId) {
		super();
		this.userId = userId;
		this.profileId = profileId;
	}

	public boolean authenticateFeatures(AuthenticationParameters params) {
		boolean result = true; 
		result = result & params.getBrowserFeature().matchesProfile(this);
		result = result & params.getDayOfWeekFeature().matchesProfile(this);
		result = result & params.getIpAddressFeature().matchesProfile(this);
		result = result & params.getOperatingSystemFeature().matchesProfile(this);
		result = result & params.getTimeOfDayFeature().matchesProfile(this);
		result = result & params.getTimeToTypePasswordFeature().matchesProfile(this);
		return result;
	}
	
	public void addHistoryEntry(HistoryEntry entry) {
		entries.add(entry);
	}

	public Integer getBroswer() {
		return broswerVendor;
	}

	public Integer getDayOfWeek() {
		return dayOfWeek;
	}

	public Collection<HistoryEntry> getEntries() {
		return entries;
	}

	public String getIpAddress() {
		return ipAddress;
	}

	public Integer getOperatingSystem() {
		return operatingSystem;
	}

	public Integer getProfileId() {
		return profileId;
	}

	public Integer getTimeOfDay() {
		return timeOfDay;
	}

	public Integer getTimeToTypePassword() {
		return timeToTypePassword;
	}

	public Integer getUserId() {
		return userId;
	}

	public void setBroswerVendor(Integer broswerVendor) {
		this.broswerVendor = broswerVendor;
	}

	public void setDayOfWeek(Integer dayOfWeek) {
		this.dayOfWeek = dayOfWeek;
	}

	public void setIpAddress(String ipAddress) {
		this.ipAddress = ipAddress;
	}

	public void setOperatingSystem(Integer operatingSystem) {
		this.operatingSystem = operatingSystem;
	}

	public void setTimeOfDay(Integer timeOfDay) {
		this.timeOfDay = timeOfDay;
	}

	public void setTimeToTypePassword(Integer timeToTypePassword) {
		this.timeToTypePassword = timeToTypePassword;
	}

	public void update(AuthenticationParameters params) {
		Collection<HistoryEntry> entries = getEntries();
		if (entries.size() < MAX_HISTORY_ENTRIES) {
			createNewEntry(params, entries.size());
		} else {
			//find oldest
			Iterator<HistoryEntry> iterator = entries.iterator();
			HistoryEntry oldestEntry = iterator.next();
			int highestSequenceNumber = oldestEntry.getSequenceNumber();
			while (iterator.hasNext()) {
				HistoryEntry possiblyOlder = iterator.next();
				if (possiblyOlder.getSequenceNumber() < oldestEntry.getSequenceNumber()) {
					oldestEntry = possiblyOlder;
				}
				if (highestSequenceNumber < possiblyOlder.getSequenceNumber()) {
					highestSequenceNumber = possiblyOlder.getSequenceNumber();
				}
			}
			//update oldest
			oldestEntry.setSequenceNumber(highestSequenceNumber+1);
			oldestEntry.setBrowser(params.getBrowserFeature().mapToId());
			oldestEntry.setDayOfWeek(params.getDayOfWeekFeature().mapToId());
			oldestEntry.setIpAddress(params.getIpAddressFeature().getRawData());
			oldestEntry.setOperatingSystem(params.getOperatingSystemFeature().mapToId());
			oldestEntry.setTimeOfDay(params.getTimeOfDayFeature().mapToId());
			oldestEntry.setTimeToTypePassword(params.getTimeToTypePasswordFeature().getTime());
		}
		determineFeaturesFromHistory();
	}
	
	private HistoryEntry createNewEntry(AuthenticationParameters params, int sequenceNumber) {
		HistoryEntry entry = new HistoryEntry(profileId, -1);
		entry.setBrowser(params.getBrowserFeature().mapToId());
		entry.setDayOfWeek(params.getDayOfWeekFeature().mapToId());
		entry.setIpAddress(params.getIpAddressFeature().getRawData());
		entry.setOperatingSystem(params.getOperatingSystemFeature().mapToId());
		entry.setSequenceNumber(sequenceNumber);
		entry.setTimeOfDay(params.getTimeOfDayFeature().mapToId());
		entry.setTimeToTypePassword(params.getTimeToTypePasswordFeature().getTime());
		return entry;
	}

	private void determineFeaturesFromHistory() {
		LinkedHashMap<Integer, Integer> browserCounts = new LinkedHashMap<Integer, Integer>();
		LinkedHashMap<Integer, Integer> dayOfWeekCounts = new LinkedHashMap<Integer, Integer>();
		LinkedHashMap<Integer, Integer> osCounts = new LinkedHashMap<Integer, Integer>();
		LinkedHashMap<Integer, Integer> timeOfDayCounts = new LinkedHashMap<Integer, Integer>();
		for (HistoryEntry entry : getEntries()) {
			if (browserCounts.containsKey(entry.getBrowser())) {
				browserCounts.put(entry.getBrowser(), browserCounts.get(entry.getBrowser())+1);
			} else {browserCounts.put(entry.getBrowser(), 1);}
			
			if (dayOfWeekCounts.containsKey(entry.getDayOfWeek())) {
				dayOfWeekCounts.put(entry.getDayOfWeek(), dayOfWeekCounts.get(entry.getDayOfWeek())+1);
			} else {dayOfWeekCounts.put(entry.getDayOfWeek(), 1);}
			
			if (osCounts.containsKey(entry.getOperatingSystem())) {
				osCounts.put(entry.getOperatingSystem(), osCounts.get(entry.getOperatingSystem())+1);
			} else {osCounts.put(entry.getOperatingSystem(), 1);}
			
			if (timeOfDayCounts.containsKey(entry.getTimeOfDay())) {
				timeOfDayCounts.put(entry.getTimeOfDay(), timeOfDayCounts.get(entry.getTimeOfDay())+1);
			} else {timeOfDayCounts.put(entry.getTimeOfDay(), 1);}
		}
		for (Integer k : browserCounts.keySet()) {
			if (browserCounts.get(k) >= MIN_NEEDED_TO_MATCH) {broswerVendor = k; break;}
		}
		for (Integer k : dayOfWeekCounts.keySet()) {
			if (dayOfWeekCounts.get(k) >= MIN_NEEDED_TO_MATCH) {dayOfWeek = k; break;}
		}
		for (Integer k : osCounts.keySet()) {
			if (osCounts.get(k) >= MIN_NEEDED_TO_MATCH) {operatingSystem = k; break;}
		}
		for (Integer k : timeOfDayCounts.keySet()) {
			if (timeOfDayCounts.get(k) >= MIN_NEEDED_TO_MATCH) {timeOfDay = k; break;}
		}
		
		//Update password timing
		if (getEntries().size() == MAX_HISTORY_ENTRIES) {
			int sum = 0;
			for (HistoryEntry e : getEntries()) {sum +=e.getTimeToTypePassword();}
			timeToTypePassword = sum/MAX_HISTORY_ENTRIES;
		}
		
		//Update IpAddress
		List<String> ips = new LinkedList<String>();
		for (HistoryEntry e : getEntries()) {ips.add(e.getIpAddress());}
		ipAddress = IpChecker.aggregateIps(ips, MIN_NEEDED_TO_MATCH);
	}
}
